﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Text;
using System.Xml;

namespace Jayden.Dll.PhoneNumber
{
	public enum DialingPlan
	{
		// groups in closed dialing plans must compose whole number
		Closed,
		// groups in open dialing plans can be dialed with short form within the group
		Open
	}
	public class PhoneGroup
	{
		private string m_Value;
		private string m_Zone;
		private string m_Country;
		private DialingPlan m_DialingPlan = DialingPlan.Closed;
		private string m_City;
		private int? m_Digits;
		private string m_Name;
		private string m_Type;
		private string m_Format;
		private int m_FormatDigits;
		private RegionInfo m_Region;
		private PhoneGroup m_Parent;

		private string m_Prefix;
		private string m_Trunk;

		private List<PhoneGroup> m_SubGroups = new List<PhoneGroup>();

		private void AssignFromParent(PhoneGroup parent)
		{
			if (parent != null)
			{
				m_Parent = parent;
				m_Zone = parent.m_Zone;
				m_Country = parent.m_Country;
				m_City = parent.m_City;
				m_Region = parent.m_Region;
				m_DialingPlan = parent.m_DialingPlan;
				m_Type = parent.m_Type;
				m_Format = parent.m_Format;
				m_FormatDigits = parent.m_FormatDigits;

				if (parent.m_Digits.HasValue && !m_Digits.HasValue)
				{
					int length = 0;
					if (m_Value != null)
						length = m_Value.Length;
					m_Digits = parent.m_Digits - length;
				}

				if (parent.m_Format != null)
				{
					m_Format = parent.m_Format;
					m_FormatDigits = parent.m_FormatDigits;
				}
			}
		}

		private void AssignFromXml(PhoneGroup parent, XmlElement elt)
		{
			XmlAttribute value = elt.Attributes["value"];
			XmlAttribute zone = elt.Attributes["zone"];
			XmlAttribute name = elt.Attributes["name"];
			XmlAttribute plan = elt.Attributes["plan"];
			XmlAttribute format = elt.Attributes["format"];
			XmlAttribute country = elt.Attributes["country"];
			XmlAttribute city = elt.Attributes["city"];
			XmlAttribute prefix = elt.Attributes["prefix"];
			XmlAttribute trunk = elt.Attributes["trunk"];
			XmlAttribute digits = elt.Attributes["digits"];
			XmlAttribute type = elt.Attributes["type"];
			if (value != null)
			{
				m_Value = value.Value;

				if (parent != null && parent.m_Digits.HasValue)
				{
					int length = m_Value.Length;
					m_Digits = parent.m_Digits - length;
				}
			}
			if (zone != null)
				m_Zone = zone.Value;
			if (type != null)
				m_Type = type.Value;
			if (country != null)
			{
				m_Country = country.Value;
				try
				{
					m_Region = new RegionInfo(m_Country);
				}
				catch (Exception e)
				{
					if (m_Parent != null)
						m_Region = m_Parent.m_Region;
					else
						m_Region = null;
				}
			}
			if (city != null)
				m_City = city.Value;
			if (trunk != null)
				m_Trunk = trunk.Value;
			if (prefix != null)
				m_Prefix = prefix.Value;
			if (digits != null)
				m_Digits = int.Parse(digits.Value);
			if (name != null)
				m_Name = name.Value;
			if (plan != null)
				Enum.TryParse<DialingPlan>(plan.Value, out m_DialingPlan);

			if (format != null && m_Digits.HasValue)
			{
				m_Format = format.Value;
				m_FormatDigits = m_Digits.Value;
			}

			foreach (XmlNode child in elt.ChildNodes)
				if (child is XmlElement && child.Name == "Group")
					m_SubGroups.Add(new PhoneGroup(this, child as XmlElement));
		}

		public PhoneGroup(PhoneGroup parent, XmlElement elt)
		{
			AssignFromParent(parent);
			AssignFromXml(parent, elt);
		}
		public PhoneGroup(PhoneGroup parent, string value, string name)
		{
			m_Value = value;
			AssignFromParent(parent);
			m_Name = name;
		}
		public PhoneGroup(string file)
		{
			XmlDocument document = new XmlDocument();
			document.Load(file);
			AssignFromXml(null, document.DocumentElement);
		}

		private static PhoneGroup m_Instance = null;
		public static PhoneGroup Instance
		{
			get
			{
				if (m_Instance == null)
				{
					using (Stream stream = Assembly.GetAssembly(typeof(PhoneGroup)).GetManifestResourceStream("Jayden.Dll.PhoneNumber.PhoneNumbers.xml"))
					{
						XmlDocument document = new XmlDocument();
						document.Load(stream);
						PhoneGroup instance = new PhoneGroup(null, document.DocumentElement);
						m_Instance = instance;
					}
				}
				return m_Instance;
			}
		}
		public static PhoneGroup NewInstance()
		{
			using (Stream stream = Assembly.GetAssembly(typeof(PhoneGroup)).GetManifestResourceStream("Jayden.Dll.PhoneNumber.PhoneNumbers.xml"))
			{
				XmlDocument document = new XmlDocument();
				document.Load(stream);
				PhoneGroup instance = new PhoneGroup(null, document.DocumentElement);
				return instance;
			}
		}

		public IEnumerable<PhoneGroup> Search(string name, string number)
		{
			if (m_Name != null && name != null && m_Name.ToLowerInvariant().Contains(name))
				yield return this;
			else if (number != null && FullValue.Contains(number))
				yield return this;
			if (HaveSubGroups)
			{
				foreach (PhoneGroup g in m_SubGroups)
					foreach (PhoneGroup result in g.Search(name, number))
						yield return result;
			}
		}

		public PhoneGroup Add(string phoneNumber, string name)
		{
			PhoneGroup group = new PhoneGroup(this, phoneNumber, name);
			if (m_SubGroups == null)
				m_SubGroups = new List<PhoneGroup>();
			m_SubGroups.Add(group);
			return group;
		}

		public PhoneGroup Find(string fullNumber, int index)
		{
			for (int c = 0; c < m_Value.Length; c++)
			{
				if (m_Value[c] != fullNumber[index + c])
					return null;
			}
			PhoneGroup bestResult = null;
			if (HaveSubGroups)
			{
				foreach (PhoneGroup group in m_SubGroups)
				{
					PhoneGroup result = group.Find(fullNumber, index + m_Value.Length);
					if (result != null && (bestResult == null || bestResult.FullValue.Length < result.FullValue.Length))
						bestResult = result;
						
				}
			}
			return bestResult ?? this;
		}

		public PhoneGroup Insert(string fullNumber, string name)
		{
			PhoneGroup result = Find(fullNumber, 0);
			if (result != null)
			{
				string phoneNumber = fullNumber.Substring(result.FullValue.Length);
				if (phoneNumber.Length > 0)
					return result.Add(phoneNumber, name);
			}
			return null;
		}

		public string Zone { get { return m_Zone; } }
		public string Country { get { return m_Country; } }
		public string City { get { return m_City; } }
		public string Type { get { return m_Type; } }
		public int? Digits { get { return m_Digits; } }
		public PhoneGroup Parent { get { return m_Parent; } }
		public string Value { get { return m_Value; } }
		public string Name { get { return m_Name; } }
		public IEnumerable<PhoneGroup> SubGroups { get { return m_SubGroups; } }

		public bool HaveSubGroups { get { return m_SubGroups != null && m_SubGroups.Count > 0; } }

		public string FullValue { get { return m_Parent == null ? m_Value : (m_Value == null ? m_Parent.FullValue : m_Parent.FullValue + m_Value); } }
		public string FilledValue
		{
			get
			{
				string fullValue = FullValue;
				if (m_Digits.HasValue)
					for (int c = 0; c < m_Digits.Value; c++)
						fullValue += "x";
				return fullValue;
			}
		}

		public string FilledFormattedValue
		{
			get
			{
				string filledValue = FilledValue;
				if (m_Format == null)
					return filledValue;
				string numbers = filledValue.Substring(filledValue.Length - m_FormatDigits);
				int index = 0;
				StringBuilder builder = new StringBuilder();
				for (int c = 0; c < m_Format.Length; c++)
				{
					if (m_Format[c] == 'x')
					{
						builder.Append(numbers[index]);
						index++;
					}
					else
						builder.Append(m_Format[c]);
				}
				return builder.ToString();
			}
		}

		public override string ToString()
		{
			string filledValue = FilledValue;
			if (m_Region != null)
				filledValue += " " + m_Region.TwoLetterISORegionName;
			return filledValue;
		}

	}
}
